#!/usr/bin/python3
import os, sys
import re
from io import StringIO, BytesIO
from PIL import Image, ImageDraw, ImageFont
import pygame

import progressbar
import shutil
"""
读取字体文件和示例文本生成预览图片
"""

g_re_first_word = re.compile((u""
    + u"(%(prefix)s+\S%(postfix)s+)" # 标点
    + u"|(%(prefix)s*\w+%(postfix)s*)" # 单词
    + u"|(%(prefix)s+\S)|(\S%(postfix)s+)" # 标点
    + u"|(\d+%%)" # 百分数
    ) % {
    "prefix": u"['\"\(<\[\{‘“（《「『]",
    "postfix": u"[:'\"\)>\]\}：’”）》」』,;\.\?!，、；。？！]",
})

pygame.init()

def getFontForPyGame(font_name="msyh.ttc", font_size=14):
    return pygame.font.Font(font_name, font_size)


def makeConfig(cfg=None):
    if not cfg or type(cfg) != dict:
        cfg = {}
    default_cfg = {
        "width": 440, # px
        "padding": (15, 18, 20, 18),
        "font-size": 14, # px
        "title-font-size": 24, # px
        "font-family": "sarasa-regular.ttc",
        "font-color": (0, 0, 0),
        "font-antialiasing": True, # 字体是否反锯齿
        "background-color": (255, 255, 255),
        "border-size": 1,
        "border-color": (192, 192, 192),
        "copyright": u"本图文由 txt2.im 自动生成，但不代表 txt2.im 赞同其内容或立场。",
        "first-line-as-title": True,
        "break-word": False,
    }
    default_cfg.update(cfg)
    return default_cfg


def makeLineToWordsList(line, break_word=False):
    u"""将一行文本转为单词列表"""
    if break_word:
        return [c for c in line]
    lst = []
    while line:
        ro = g_re_first_word.match(line)
        end = 1 if not ro else ro.end()
        lst.append(line[:end])
        line = line[end:]
    return lst


def makeLongLineToLines(long_line, start_x, start_y, width, height, font, cn_char_width=0):
    u"""将一个长行分成多个可显示的短行"""

    txt = long_line
    if not txt:
        return [None]
    words = makeLineToWordsList(txt)
    lines = []
    if not cn_char_width:
        cn_char_width, h = font.size(u"汉")
    avg_char_per_line = int(width / cn_char_width)
    if avg_char_per_line <= 1:
        avg_char_per_line = 1
    line_x = start_x
    line_y = start_y
    while words:
        tmp_words = words[:avg_char_per_line]
        tmp_ln = "".join(tmp_words)
        w, h = font.size(tmp_ln)
        wc = len(tmp_words)
        while w < width and wc < len(words):
            wc += 1
            tmp_words = words[:wc]
            tmp_ln = "".join(tmp_words)
            w, h = font.size(tmp_ln)
        while w > width and len(tmp_words) > 1:
            tmp_words = tmp_words[:-1]
            tmp_ln = "".join(tmp_words)
            w, h = font.size(tmp_ln)
            
        if w > width and len(tmp_words) == 1:
            # 处理一个长单词或长数字
            line_y = makeLongWordToLines(
                tmp_words[0], line_x, line_y, width, height, font, lines
            )
            words = words[len(tmp_words):]
            continue

        line = {
            "x": line_x,
            "y": line_y,
            "text": tmp_ln,
            "font": font,
        }

        line_y += height
        words = words[len(tmp_words):]
        lines.append(line)
        if len(lines) >= 1:
            # 去掉长行的第二行开始的行首的空白字符
            while len(words) > 0 and not words[0].strip():
                words = words[1:]
    return lines


def makeLongWordToLines(long_word, line_x, line_y, width, height, font, lines):

    if not long_word:
        return line_y
    c = long_word[0]
    char_width, char_height = font.size(c)
    default_char_num_per_line = int(width / char_width)

    while long_word:
        tmp_ln = long_word[:default_char_num_per_line]
        w, h = font.size(tmp_ln)
        
        l = len(tmp_ln)
        while w < width and l < len(long_word):
            l += 1
            tmp_ln = long_word[:l]
            w, h = font.size(tmp_ln)
        while w > width and len(tmp_ln) > 1:
            tmp_ln = tmp_ln[:-1]
            w, h = font.size(tmp_ln)

        l = len(tmp_ln)
        long_word = long_word[l:]

        line = {
            "x": line_x,
            "y": line_y,
            "text": tmp_ln,
            "font": font,
            }
        line_y += height
        lines.append(line)
    return line_y


def makeMatrix(txt, font, titleFont, cfg):

    width = cfg["width"]
    data = {
        "width": width,
        "height": 0,
        "lines": [],
    }

    a = txt.split("\n")
    cur_x = cfg["padding"][3]
    cur_y = cfg["padding"][0]

    for ln_idx, ln in enumerate(a):
        ln = ln.rstrip()
        if ln_idx == 0 and cfg["first-line-as-title"]:
            f = titleFont
            cn_char_width, h = titleFont.size(u"汉")
        else:
            f = font
            cn_char_width, h = font.size(u"汉")
        current_width = width - cur_x - cfg["padding"][1]
        lines = makeLongLineToLines(ln, cur_x, cur_y, current_width, h, f, cn_char_width=cn_char_width)
        cur_y += h * len(lines)
        data["lines"].extend(lines)
    data["height"] = cur_y + cfg["padding"][2]
    return data


def makeImage(data, cfg):
    u""""""
    width, height = data["width"], data["height"]
    if cfg["copyright"]:
        height += 48
    im = Image.new("RGB", (width, height), cfg["background-color"])
    dr = ImageDraw.Draw(im)
    white_block_list = []
    for ln_idx, line in enumerate(data["lines"]):
        obj = __makeLine(im, line, cfg)
        if obj is not None:
            white_block_list.append(obj)
    # 缩放
#    im = im.resize((width / 2, height / 2), Image.ANTIALIAS)

    drawBorder(im, dr, cfg)
    drawCopyright(im, dr, cfg)
    return white_block_list, im


def drawCopyright(im, dr, cfg):
    u"""绘制版权信息"""
    if not cfg["copyright"]:
        return
    font = getFontForPyGame(font_name=cfg["font-family"], font_size=12)
    rtext = font.render(cfg["copyright"],
        cfg["font-antialiasing"], (128, 128, 128), cfg["background-color"]
    )
    sio = BytesIO()
    pygame.image.save(rtext, sio)
    sio.seek(0)
    copyright_im = Image.open(sio)

    iw, ih = im.size
    cw, ch = rtext.get_size()
    padding = cfg["padding"]

    offset_y = ih - 32 - padding[2]
    cx = cfg["padding"][3]
    cy = offset_y + 12
    dr.line([(padding[3], offset_y), (iw - padding[1], offset_y)], width=1, fill=(192, 192, 192))
    im.paste(copyright_im, (cx, cy))


def drawBorder(im, dr, cfg):
    u"""绘制边框"""
    if not cfg["border-size"]:
        return
    w, h = im.size
    x, y = w - 1, h - 1
    dr.line(
        [(0, 0), (x, 0), (x, y), (0, y), (0, 0)],
        width=cfg["border-size"],
        fill=cfg["border-color"],
    )


def __makeLine(im, line, cfg):
    """"""
    if not line:
        return
    sio = BytesIO()
    x, y = line["x"], line["y"]
    text = line["text"]
    font = line["font"]
    rtext = font.render(text, cfg["font-antialiasing"], cfg["font-color"], cfg["background-color"])
    pygame.image.save(rtext, sio)

    sio.seek(0)
    ln_im = Image.open(sio)
    if len(ln_im.getcolors()) == 1:
        return False
    im.paste(ln_im, (x, y))
    return True


def txt2im(txt, outfn, cfg=None, show=False):
    """"""
    cfg = makeConfig(cfg)
    font = getFontForPyGame(cfg["font-family"], cfg["font-size"])
    titleFont = getFontForPyGame(cfg["font-family"], cfg["title-font-size"])
    data = makeMatrix(txt, font, titleFont, cfg)
    white_block_list, im = makeImage(data, cfg)
    if white_block_list.count(False)/len(white_block_list) > 0.9:
        return False
    im.save(outfn)
    if os.name == "nt" and show:
        im.show()
    return True


def resolveBadFonts(bad_list, dir='bad'):
    if len(bad_list) == 0:
        return
    print('No review fonts list:{}'.format(bad_list))
    if not os.path.exists(dir):
        os.mkdir(dir)
    for file in bad_list:
        if os.path.exists(os.path.join(dir,os.path.basename(file))):
            os.remove(file)
        else:
            shutil.move(file, dir)


def run(dir, img_dir, cfg, text, extensions=['.ttf', '.otf', '.ttc'], rewrite=False):
    """main run"""
    if not os.path.exists(img_dir):
        os.mkdir(img_dir)
    if not os.path.exists(dir):
        print('Target font directory:"{}" does not exists.'.format(dir))
        return
    print('Start generate "{}" folder...'.format(dir))
    widgets = ['Progress: ', progressbar.Percentage(), ' ', progressbar.Bar(marker='#'), ' ',
               progressbar.Timer(), ' ', progressbar.ETA()]
    fileList = os.listdir(dir)
    pbar = progressbar.ProgressBar(widgets=widgets, max_value=10*len(fileList)).start()
    move_list = []
    message = ''
    for i, p in enumerate(fileList):
        file_path = os.path.join(dir, p)
        if os.path.isfile(file_path):
            if any(os.path.splitext(p)[-1].lower() == i for i in extensions):
                try:
                    img_path = os.path.join(img_dir,os.path.splitext(p)[0]+'.png')
                    if not os.path.exists(img_path) or rewrite:
                        cfg['font-family'] = file_path
                        success = txt2im(text, img_path, cfg, show=False)
                        if not success:
                            move_list.append(file_path)
                    else:
                        message += f'"{file_path}"\'preview is existsed，and rewrite is off, skipped it.\n'
                    pbar.update(10 * i + 1)
                except Exception as e:
                    message += f'{file_path},  ERR: , {e}\n'
                    move_list.append(file_path)
    pbar.finish()
    print('Finished', ', message as follows：\n'+message if message else '.')
    resolveBadFonts(move_list)

    
if __name__ == "__main__":
    # 字体文件夹列表
    dirs = ['Fonts']
    # 预览存放文件夹，默认是同文件夹
    img_dirs = [i for i in dirs]
    # 配置
    cfg = {'font-size':50, 'title-font-size':80,
           'width':1080}
    # 是否覆盖已存在预览
    rewrite = False
    if not os.path.exists("text.txt"):
        print('未找到示例文本"text.txt"')
        sys.exit(0)
    else :
        #text = open("text.txt", "rb").read().decode("utf-8")
        with open("text.txt", "r", encoding='utf-8') as f:
            text = f.read()
    print('Start run...')
    for i, dir in enumerate(dirs):
        run(dir, img_dirs[i], cfg, text, rewrite=rewrite)